package com.hgd.evp.common.utils;

import com.alibaba.fastjson.JSONObject;
import com.hgd.evp.common.annotation.MergeField;
import com.hgd.evp.common.constances.Constances;
import com.hgd.evp.common.entity.MergeEntity;
import com.hgd.evp.common.entity.ToMergedEntity;
import com.hgd.evp.common.exception.IllegalArgsException;
import org.springframework.stereotype.Component;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author 尚村山夫
 */
@Component
public class MergeUtil {
    public <T> void merge(Class<T> clazz, List<T> list) throws InvocationTargetException, IllegalAccessException, InstantiationException, IllegalArgsException {
        if (EmptyUtil.isEmpty(list)) {
            return;
        }

        List<MergeEntity> meList = new ArrayList<>();
        List<ToMergedEntity<T>> tmeList = new ArrayList<>();

        mergeFieldAssistThree(clazz, list, meList, tmeList);
        mergeFieldAssistFour(meList, tmeList);
    }

    private <T> void mergeFieldAssistThree(Class<T> clazz, List<T> list, List<MergeEntity> meList,
                                                  List<ToMergedEntity<T>> tmeList)
            throws IllegalAccessException, InvocationTargetException, IllegalArgsException {
        for (T t : list) {
            PropertyDescriptor[] toMergedPd = BeanUtil.getPropertyDescriptors(clazz);

            for (PropertyDescriptor pd : toMergedPd) {
                Method toMergedWriteMethod = pd.getWriteMethod();
                Method toMergedReadMethod = pd.getReadMethod();

                if (EmptyUtil.isEmpty(toMergedWriteMethod)) {
                    continue;
                }

                MergeField writeAnnotation = toMergedWriteMethod.getAnnotation(MergeField.class);
                if (EmptyUtil.isNotEmpty(writeAnnotation)) {
                    Object args = toMergedReadMethod.invoke(t);

                    String annoMethodInPOJOStr = writeAnnotation.method();
                    Class<?> annoClass = writeAnnotation.clazz();

                    /*
                     * 该方法名是被合并数据的来源方法，如：
                     * com.hgd.admin.feign.FakeFeign.getUserName
                     * 该名称以字符串形式，在实体类与来源方法之间做对应关系,如：
                     * 在某实体类的setter方法上，我们添加有如下注解
                     * @MergeField(clazz = FakeFeign.class,method = "getUserName")
                     * 该注解即可定位到com.hgd.admin.feign.FakeFeign类的getUserName方法
                     *
                     */
                    String methodInTME = annoClass.getName() + Constances.SHARP + annoMethodInPOJOStr;

                    Method annoMethod = BeanUtil.findMethodWithMinimalParameters(annoClass, annoMethodInPOJOStr);

                    MergeEntity temporaryMe = new MergeEntity(annoClass, annoMethod, String.valueOf(args));

                    /*
                     * 在诸多待合并的实体类中，有可能多个方法上使用的是同一FakeFeign中的同一方法，只是方法参数不同
                     * 因此，对我们来说，没必要生成多个MergeEntity对象来保存这些信息
                     * 我们采用的方法是：(FakeFeign类+类中的某个方法)使用一个MergeEntity对象封装，方法中的参数用Set集合进行
                     * 累加封装
                     */
                    if (meList.indexOf(temporaryMe) < 0) {
                        // meList中尚未包含temporaryMe对象
                        meList.add(temporaryMe);
                    } else {
                        // 说明meList中已经包含temporaryMe对象，如果其参数不为空，则将其添加到参数集中
                        if (EmptyUtil.isNotEmpty(temporaryMe.getFeignArgs())) {
                            // 获取temporaryMe在集合中的位置
                            int i = meList.indexOf(temporaryMe);
                            meList.get(i).getFeignArgs().addAll(temporaryMe.getFeignArgs());
                        }
                    }

                    Object invoke = toMergedReadMethod.invoke(t);
                    if (!(invoke instanceof String)) {
                        throw new IllegalArgsException("被合并的方法返回值期望是'java.lang.String'类型");
                    }
                    ToMergedEntity<T> tme = new ToMergedEntity<>(t, toMergedWriteMethod,
                            toMergedReadMethod, methodInTME, String.valueOf(invoke));
                    tmeList.add(tme);
                }
            }
        }
    }

    private <T> void mergeFieldAssistFour(List<MergeEntity> meList, List<ToMergedEntity<T>> tmeList)
            throws IllegalAccessException, InvocationTargetException, InstantiationException {
        for (MergeEntity me : meList) {
            Set<String> args = me.getFeignArgs();
            Method annoMethod = me.getFeignMethod();
            Class<?> annoClazz = me.getFeignClazz();

            // 获取我们待合并字段对应的值,其类型应为JSONObject或Map
            Object bean = SpringContextUtil.getBean(annoClazz.getName());
            Object expectValue = annoMethod.invoke(bean, String.join(",", args));

            if (EmptyUtil.isEmpty(expectValue)) {
                // 如果返回值为null,则不再进行合并操作
                return;
            }

            JSONObject keyValueJObj = new JSONObject();
            if (expectValue instanceof JSONObject) {
                keyValueJObj = (JSONObject) expectValue;
            } else if (expectValue instanceof Map) {
                Map invoke1 = (Map) expectValue;
                keyValueJObj = JSONObject.parseObject(JSONObject.toJSONString(invoke1));
            } else {
                throw new InstantiationException(annoMethod + "返回参数错误，期望返回Map集合或JSONObject对象");
            }

            for (ToMergedEntity t : tmeList) {
                // t为封装有待合并实体的对象
                Method writeMethod = t.getWriteMethod();
                Method readMethod = t.getReadMethod();

                // 待粘合期望值的方法,该方法通常定义在VO类内
                Method stickExpectValueMethod = BeanUtil.findMethodWithMinimalParameters(t.getData().getClass(),
                        writeMethod.getName() + Constances.MERGE_FIELD_SUFFIX);

                String s = annoClazz
                        .getName() + Constances.SHARP + annoMethod.getName();
                String feignMethodInPOJOStr = t.getFeignMethod();

                boolean equals = StringUtil.equals(s, feignMethodInPOJOStr);

                if (EmptyUtil.isNotEmpty(stickExpectValueMethod) && equals) {
                    Object invoke1 = readMethod.invoke(t.getData());
                    stickExpectValueMethod.invoke(t.getData(), keyValueJObj.getString(String.valueOf(invoke1)));
                }
            }
        }
    }
}
